package org.xtlgen;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Element;

/**
 * Represents a Voronoi in the XtlGen system.
 * <p>
 * A Voronoi must be specified like this:
 * </p>
 * <pre>
 * &lt;Voronoi&gt;
 *   &lt;Point1 x="[x_position]" y="[y_position]"/&gt;
 *   &lt;Point2 x="[x_position]" y="[y_position]"&gt;
 *     [subshapes]
 *   &lt;/Point1&gt;
 *   [more points]
 * &lt;/Voronoi&gt;
 * </pre>
 * in the configuration file.
 */
public class Voronoi extends ClosedShape {
  /**
   * Construct a Voronoi.
   *
   * @param parent  the parent Shape, null allowed
   * @param params  parameters for this Shape as read from the config file
   */
  public Voronoi(Shape parent, Element params) {
    super(parent, params);
  }
  
  /**
   * Create the encapsulated AWT shape, the parent's AWT shape is returned.
   * This method takes care of creating the cell polygons and creating and
   * associating subshapes of them given as child nodes of the enclosing
   * point.
   *
   * @param params  parameters for this Shape as read from the config file
   *
   * @return the parent's AWT shape
   */
  protected java.awt.Shape createAWTShape(Element params) {
    java.awt.Shape result = getParent().getAWTShape();
    NodeList pointNodes = getPoints(params);
    
    ArrayList<Point> points = new ArrayList<Point>();
    double minX=Double.MAX_VALUE;
    double minY=Double.MAX_VALUE;
    double maxX=Double.MIN_VALUE;
    double maxY=Double.MIN_VALUE;

    // Obtain a list of all the points and calculate the bounding box for
    // all of them, the VoronoiGenerator needs this to create the initial
    // triangle for the algorithm.
    try {
      for (int i=0; i<pointNodes.getLength(); i++) {
        Point newPoint = new Point(getParent(), (Element)pointNodes.item(i));
        points.add(newPoint);
        
        if (newPoint.getX() < minX) {
          minX = newPoint.getX();
        }
        else if (newPoint.getX() > maxX) {
          maxX = newPoint.getX();
        }
        
        if (newPoint.getY() < minY) {
          minY = newPoint.getY();
        }
        else if (newPoint.getY() > maxY) {
          maxY = newPoint.getY();
        }
      }
    }
    catch(IllegalArgumentException e) {
      throw new IllegalArgumentException("Voronoi: " + e.getMessage());
    }

    // Create the VoronoiGenerator
    VoronoiGenerator vg = new VoronoiGenerator(minX, minY, maxX, maxY);

    // Add all points
    for (Point point : points) {
      vg.addPoint(point);
    }

    // Get the vertices for the bounding cell's polygon for each point, create
    // the Polygon and add its children.
    
    for (int i=0; i<pointNodes.getLength(); i++) {
      Polygon poly = new Polygon(getParent(), vg.getVertices(points.get(i)));
      
      try {
        NodeList subShapeNodes = pointNodes.item(i).getChildNodes();
        for (int j=0; j<subShapeNodes.getLength(); j++) {
          if (subShapeNodes.item(j).getNodeType() == Node.ELEMENT_NODE)
          {
            poly.addChild(Shape.makeShape(poly, (Element)subShapeNodes.item(j)));
          }
        }
      }
      catch(IllegalArgumentException e) {
        throw new IllegalArgumentException("Voronoi: " + e.getMessage());
      }
        
      getParent().addChild(points.get(i));
      getParent().addChild(poly);
    }
    
    return result;
  }

  /*
   * Get the list of Points for this Voronoi. Cannot use getElementsByTagName
   * because it returns subchild points.
   */
  private NodeList getPoints(Element params) {
    NodeList allNodes = params.getChildNodes();
    ParamList points = new ParamList();

    for (int i=0; i<allNodes.getLength(); i++) {
      Node n = allNodes.item(i);
      if (n.getNodeName().compareTo("Point") == 0) {
        points.add(n);
      }
    }

    return points;
  }
  
  /*
   * Static block of code used to register a ShapeFactory for Voronoi.
   */
  static {
    Shape.registerShapeFactory(
      "Voronoi",
      new Shape.ShapeFactory() {
        public Shape makeShape(Shape parent, Element params) {
          return new Voronoi(parent, params);
        }
      });
  }
  
}